package lion.framework.core.util;

import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.jar.JarFile;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import lion.dev.lang.MapJ;
import lion.framework.core.conf.Config;
import lion.framework.core.conf.ConfigManager;

/**
 * @author lion
 * @email hl_0074@sina.com
 * @date 2015年3月16日
 */
public class ClassScanner {

	private static final String FRAMEWORK_BASE_PACKAGE = "lion";
	private static Log log = LogFactory.getLog(ClassScanner.class);
	private List<String> configedPackages = new ArrayList<>();
	private static ClassScanner instance;

	public ClassScanner() {

		Config config = ConfigManager.getConfig("framework");

		List<MapJ> paths = config.getList("framework.package-scan");
		for (MapJ m : paths) {
			String p = m.getString("base");
			if (!configedPackages.contains(p)) {
				configedPackages.add(p);
			}
		}
	}

	public static List<Class<?>> scan(File file) {

		if (!file.exists()) { return new ArrayList<>(); }

		return getInstance().scanClass(file);
	}

	private static ClassScanner getInstance() {

		if (instance == null) {
			instance = new ClassScanner();
		}
		return instance;
	}

	private List<Class<?>> scanClass(File file) {

		List<Class<?>> result = new ArrayList<>();
		scan(file, "", result);
		return result;
	}

	private void scan(File file, String basePkg, List<Class<?>> result) {

		if (file.isDirectory()) {
			for (File sub : file.listFiles()) {
				String pkg = "";
				if (StringUtils.isBlank(basePkg)) {
					pkg = sub.getName();
				} else {
					pkg = basePkg;
					if (sub.isDirectory()) {
						pkg += "." + sub.getName();
					}
				}
				scan(sub, pkg, result);
			}
		} else if (file.getName().endsWith(".jar")) {
			// 处理Jar文件
			JarFile jar = null;
			try {
				jar = new JarFile(file);
			} catch (Exception e) {
				if (log.isDebugEnabled()) {
					log.debug("error read jar:" + file.getName());
				}
			}
			if (jar == null) { return; }

			Collections.list(jar.entries()).forEach(je -> {
				String name = je.getName();
				if (name.endsWith(".class")) {
					addClass(name.replace("/", "."), result);
				}
			});
		} else if (file.getName().endsWith(".class")) {
			if (file.getName().indexOf("$") > -1) { return; }
			String classs = StringUtils.isBlank(basePkg) ? file.getName() : basePkg + "." + file.getName();
			addClass(classs, result);
		}
	}

	private void addClass(String clazz, List<Class<?>> classes) {

		// add base scanner
		if (!basePackageMatches(clazz)) { return; }

		Class<?> c = null;

		try {
			c = Class.forName(clazz.substring(0, clazz.lastIndexOf(".")));
		} catch (Throwable e) {
			if (log.isDebugEnabled()) {
				log.debug(String.format("class [%s] load error for %s ", clazz, e.getMessage() + " not found"));
			}
		}
		if (c != null) {
			classes.add(c);
		}
	}

	private boolean basePackageMatches(String clazz) {

		if (clazz.indexOf("$") > -1) { return false; }
		if (clazz.startsWith(FRAMEWORK_BASE_PACKAGE)) { return true; }
		if (configedPackages == null || configedPackages.isEmpty()) { return false; }

		for (String prefix : configedPackages) {
			if (clazz.startsWith(prefix)) { return true; }
		}
		return false;
	}
}
